/* ziproxy.c
 * 2002 Juraj Variny<variny@naex.sk>
 *
 * Released subject to GNU General Public License.
 * 
 * Based on mwp_proxy.c from Mark Williams<mwp@aussiemail.com.au>
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
#include <syslog.h>


#include "image.h"
#include "textmodify.h"

#define SERVER_NAME "ziproxy"
#define SERVER_URL ""
#define SERVER_HOST "decef"
#define PROTOCOL "HTTP/1.0"

#define TIMEOUT 90

#define MAX_SIZE 16*1024*1024
#define MAX_SIZE_STR "16 MB"

#define MAX_LINELEN 5000
#define MAX_HEADERS 50
#define RFC1123FMT "%a, %d %b %Y %H:%M:%S GMT"

typedef char *headers[MAX_HEADERS];

enum {IMG_NONE, IMG_PNG, IMG_GIF, IMG_JPEG};

#define WILLGZIP (1<<1)
#define SUFF_MODIFIED (1<<2)
#define MODIFY_HTML (1<<3)
#define CONN_KEEPALIVE (1<<4)

/* Forwards. */
static int open_client_socket( char* hostname, short port );
static headers* get_client_headers(int*, int*);
static void proxy_http( headers *lines, char* method, char* path, char* protocol, 
				FILE* sockrfp, FILE* sockwfp, int content_length, int flags);
static void proxy_ssl( char* method, char* host, int port, char* protocol, FILE* sockrfp, FILE* sockwfp );
static int check_trim( char* line );
static void send_error( int status, char* title, char* extra_header, char* text );
static void send_headers( int status, char* title, char* extra_header, char* mime_type, int length, time_t mod );
static long timeval_subtract_us ( struct timeval *x, struct timeval *y);
static char* allocstr(size_t size);
#ifdef TIMEOUT
static void sigcatch( int sig );
#endif

#ifdef LOGGING
//logging file
FILE *logfile;
struct timeval newtime, oldtime;
#endif

int logpid;
static char line[MAX_LINELEN];

int
main( int argc, char** argv )
    {
    char *method, *url, *protocol, *host, *path;
    short port;
    int sockfd;
    int ssl, flags = 0;
	int linelen,content_length;
    FILE* sockrfp;
    FILE* sockwfp;
    time_t tm;
	headers* req_hdrs;

    logpid=getpid();
	
#ifdef LOGGING
    time(&tm);
    //open logging file
#ifdef LOG_PIPE
	logfile = stderr;
#elif defined LOG_FILE
    logfile = fopen(LOG_FILE, "a");
#else
#error If you turn on LOGGING, choose betwen LOG_PIPE and LOG_FILE.
#endif

	
	//set line buffered mode for logfile
	setvbuf(logfile,NULL,_IOLBF,BUFSIZ);
	//setlinebuf(logfile);
	
    fprintf(logfile, "[%d] Starting - %s", logpid, ctime(&tm));

    gettimeofday(&oldtime,NULL);
#endif//LOGGING
	
#ifdef TIMEOUT
    /* Get ready to catch timeouts.. */
    (void) signal( SIGALRM, sigcatch );

    (void) alarm( TIMEOUT );
#endif

	/* Read the first line of the request. */
    if ((fgets(line, sizeof(line), stdin ) ==  NULL) ||	
					(linelen = check_trim(line)) == 0)
		send_error( 400, "Bad Request", (char*) 0, 
						"No request found or request too long." );
	
    method=allocstr (5*linelen);
	url = method + linelen;
	protocol = url + linelen;
	host = protocol + linelen;
	path = host + linelen;
	
    /* Parse it. */
    if ( sscanf( line, "%[^ ] %[^ ] %[^ ]", method, url, protocol ) != 3 )
		send_error( 400, "Bad Request", (char*) 0, "Can't parse request." );

    if ( url == (char*) 0 )
		send_error( 400, "Bad Request", (char*) 0, "Null URL." );

    if ( strncasecmp( url, "http://", 7 ) == 0 )
	{

		(void) strncpy( url, "http", 4 );	/* make sure it's lower case */
		if ( sscanf( url, "http://%[^:/]:%hd%s", host, &port, path ) == 3 );
		else if ( sscanf( url, "http://%[^/]%s", host, path ) == 2 )
		{
		    port = 80;
		}
		else if ( sscanf( url, "http://%[^:/]:%hd", host, &port ) == 2 )
		{
			path=url;
		}
		else if ( sscanf( url, "http://%[^/]", host ) == 1 )
		{
	    	port = 80;
			path=url;
	    }
	else
	    send_error( 400, "Bad Request", (char*) 0, "Can't parse URL." );
	ssl = 0;
#ifdef USE_EXTPROXY
	path = url;
#endif 
	if(undo_name(path)) flags |= SUFF_MODIFIED;
	}
    else if ( strcmp( method, "CONNECT" ) == 0 )
	{
	if ( sscanf( url, "%[^:]:%hd", host, &port ) == 2 )
	    ;
	else if ( sscanf( url, "%s", host ) == 1 )//equivalent to strcpy...?
	    port = 443;
	else
	    send_error( 400, "Bad Request", (char*) 0, "Can't parse URL." );
	ssl = 1;
	}
    else
	send_error( 400, "Bad Request", (char*) 0, "Unknown URL type." );

    //log URL
#ifdef LOGGING
    fprintf(logfile, "[%d] URL - %s\n",logpid , url);
#endif
	
	if(!ssl)
	{
			req_hdrs=get_client_headers(&content_length, &flags);
#ifdef LOGGING
	gettimeofday(&newtime,NULL);
	fprintf(logfile, "[%d] getting, parsing headers took %ld us \n",logpid,
			timeval_subtract_us(&newtime, &oldtime));
	fprintf(logfile, "[%d] Method = %s Protocol = %s\n",logpid, method, protocol);
	gettimeofday(&oldtime,NULL);
#endif
	}

    /* Open the client socket to the real web server. */
#ifdef TIMEOUT
    (void) alarm( TIMEOUT );
#endif
    sockfd = open_client_socket( host, port );

    /* Open separate streams for read and write, r+ doesn't always work. 
	 * What about "a+" ? */
    sockrfp = fdopen( sockfd, "r" );
    sockwfp = fdopen( sockfd, "w" );

    if ( ssl )
	proxy_ssl( method, host, port, protocol, sockrfp, sockwfp );
    else
	proxy_http( req_hdrs, method, path, protocol, sockrfp, sockwfp, content_length, flags);

    /* Done. */
#ifdef LOGGING
    fprintf(logfile, "[%d] Finished.\n",logpid);
    fclose(logfile);
#endif
    (void) close( sockfd );
    exit( 0 );
    }


#if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED)
#define USE_IPV6
#endif

static int
open_client_socket( char* hostname, short port )
    {
#ifdef USE_IPV6
    struct addrinfo hints;
    char portstr[10];
    int gaierr;
    struct addrinfo* ai;
    struct addrinfo* ai2;
    struct addrinfo* aiv4;
    struct addrinfo* aiv6;
    struct sockaddr_in6 sa;
#else /* USE_IPV6 */
    struct hostent *he;
    struct sockaddr_in sa;
#endif /* USE_IPV6 */
    int sa_len, sock_family, sock_type, sock_protocol;
    int sockfd;

    memset( (void*) &sa, 0, sizeof(sa) );

#ifdef USE_EXTPROXY
	hostname = EXTPROXY_ADDR;
	port = EXTPROXY_PORT;
#endif


#ifdef USE_IPV6
    (void) memset( &hints, 0, sizeof(hints) );
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    (void) snprintf( portstr, sizeof(portstr), "%d", port );
    if ( (gaierr = getaddrinfo( hostname, portstr, &hints, &ai )) != 0 )
	send_error( 404, "Not Found", (char*) 0, "Unknown host." );

    /* Find the first IPv4 and IPv6 entries. */
    aiv4 = (struct addrinfo*) 0;
    aiv6 = (struct addrinfo*) 0;
    for ( ai2 = ai; ai2 != (struct addrinfo*) 0; ai2 = ai2->ai_next )
	{
	switch ( ai2->ai_family )
	    {
	    case AF_INET: 
	    if ( aiv4 == (struct addrinfo*) 0 )
		aiv4 = ai2;
	    break;
	    case AF_INET6:
	    if ( aiv6 == (struct addrinfo*) 0 )
		aiv6 = ai2;
	    break;
	    }
	}

    /* If there's an IPv4 address, use that, otherwise try IPv6. */
    if ( aiv4 != (struct addrinfo*) 0 )
	{
	if ( sizeof(sa) < aiv4->ai_addrlen )
	    {
	    (void) fprintf(	stderr, 
						"%s - sockaddr too small (%lu < %lu)\n",
		hostname, (unsigned long) sizeof(sa),
		(unsigned long) aiv4->ai_addrlen );
	    exit( 1 );
	    }
	sock_family = aiv4->ai_family;
	sock_type = aiv4->ai_socktype;
	sock_protocol = aiv4->ai_protocol;
	sa_len = aiv4->ai_addrlen;
	(void) memmove( &sa, aiv4->ai_addr, sa_len );
	goto ok;
	}
    if ( aiv6 != (struct addrinfo*) 0 )
	{
	if ( sizeof(sa) < aiv6->ai_addrlen )
	    {
	    (void) fprintf(
		stderr, "%s - sockaddr too small (%lu < %lu)\n",
		hostname, (unsigned long) sizeof(sa),
		(unsigned long) aiv6->ai_addrlen );
	    exit( 1 );
	    }
	sock_family = aiv6->ai_family;
	sock_type = aiv6->ai_socktype;
	sock_protocol = aiv6->ai_protocol;
	sa_len = aiv6->ai_addrlen;
	(void) memmove( &sa, aiv6->ai_addr, sa_len );
	goto ok;
	}

    send_error( 404, "Not Found", (char*) 0, "Unknown host." );

    ok:
    freeaddrinfo( ai );

#else /* USE_IPV6 */

    he = gethostbyname( hostname );
    if ( he == (struct hostent*) 0 )
	send_error( 404, "Not Found", (char*) 0, "Unknown host." );
    sock_family = sa.sin_family = he->h_addrtype;
    sock_type = SOCK_STREAM;
    sock_protocol = 0;
    sa_len = sizeof(sa);
    (void) memmove( &sa.sin_addr, he->h_addr, he->h_length );
    sa.sin_port = htons( port );

#endif /* USE_IPV6 */

    sockfd = socket( sock_family, sock_type, sock_protocol );
    if ( sockfd < 0 )
	send_error( 500, "Internal Error", (char*) 0, "Couldn't create socket." );

    if ( connect( sockfd, (struct sockaddr*) &sa, sa_len ) < 0 )
	send_error( 503, "Service Unavailable", (char*) 0, "Connection refused." );

    return sockfd;
    }

static headers * get_client_headers(int *content_length, int *flags)
{
	//char data[MAX_LINELEN];
	int was_via=0;
	int linelen,i=0;
	headers * hdrs;

	
	hdrs=(headers*)malloc(sizeof(headers));
	*content_length=-1;
	
	while (fgets(line, sizeof(line), stdin) != NULL)
	{
		if (strcmp(line, "\n") == 0 || strcmp(line, "\r\n") == 0)
			break;
		
		if((linelen=check_trim(line))==0)
			send_error( 400, "Bad request", (char*) 0, "Line too long.");

#ifdef LOGGING
		if (strncasecmp(line,"User-Agent:", 11) == 0)
			fprintf(logfile,"[%d] %s\n",logpid, line);
#endif
		else if (strncasecmp(line,"Connection:",11) == 0)
				continue;

		//get content length
		if (strncasecmp(line, "Content-Length:", 15 ) == 0)
			*content_length = atol(&(line[15]));

			//can accept gzip?
		else if (strncasecmp(line, "Accept-Encoding:", 16) == 0)
		{
#ifdef GZIP
			*flags |= WILLGZIP;
#endif
			continue; 
		}
			//we don't know how to uncompress HTML 
			//to modify it

		if (strncasecmp(line,"Via:",4) == 0)
		{
			if(strstr(line,SERVER_HOST) && strstr(line,SERVER_NAME)) 
					/*prevent infinite recursion*/
				send_error( 503, "Service Unavailable", (char*) 0, "Connection refused." );
			
			if (i<MAX_HEADERS){
				asprintf(&((*hdrs)[i++]), "%s, 1.1 %s (%s)",
									line,SERVER_HOST,SERVER_NAME);
				was_via=1;
				continue;
			}
			else 
				send_error( 400, "Bad request", (char*) 0, "Too many headers.");
			
		}
		if(i<MAX_HEADERS) (*hdrs)[i++]=strdup(line);
		else send_error( 400, "Bad request", (char*) 0, "Too many headers.");
		
	}

	if(!was_via)
	{
			if(i<MAX_HEADERS)
					asprintf(&((*hdrs)[i++]),"Via: 1.1 %s (%s)", SERVER_HOST , SERVER_NAME);
			else	send_error( 400, "Bad request", (char*) 0, "Too many headers.");
	}
	for(;i<MAX_HEADERS;i++) (*hdrs)[i]=NULL;


	return hdrs;
}

static void proxy_http( headers* hdrs, char* method, char* path, char* protocol,
				FILE* sockrfp, FILE* sockwfp , int content_length, int flags)
{
	//char data[MAX_LINELEN];
	size_t linelen;
	int lines = 0, status = 0, ich;
	
	int where_content_type = -1,where_content_length = -1,where_chunked = -1;
	int where_content_encoding = -1;
	
	long i;

	char *tempp;
	int compress = 0;
	int quality = -1;
	int image = IMG_NONE,modify =0;

	char *inbuf;
	int inlen;

	int bpp,width,height;

	unsigned int chunklen;
	unsigned int chunkpos;
	
#ifdef TIMEOUT
	alarm(TIMEOUT);
#endif
	// Send request
	fprintf(sockwfp, "%s %s %s\r\n", method, path, protocol);

	// Forward the remainder of the request from the client

	for(i=0;((*hdrs)[i]) && i<MAX_HEADERS;i++)
	{
		fputs((*hdrs)[i],sockwfp);
		free((*hdrs)[i]);
		fputs("\r\n",sockwfp);
	}
	fputs("\r\n",sockwfp);

	// If there's content, forward that too
	if (content_length != -1)
		for (i = 0; i < content_length && (ich = getchar()) != EOF; ++i)
			putc(ich, sockwfp);
	fflush(sockwfp);

#ifdef LOGGING
	gettimeofday(&newtime,NULL);
	fprintf(logfile, "[%d] connecting, forwarding headers took %ld us \n",logpid,
			timeval_subtract_us(&newtime, &oldtime));
	gettimeofday(&oldtime,NULL);
#endif

	// Forward the response back to the client
#ifdef TIMEOUT
	alarm(TIMEOUT);
#endif
	content_length = -1;
	status = -1;

	//get header
	while (fgets(line, sizeof(line), sockrfp) != 0)
	{
		if (strcmp(line, "\n") == 0 || strcmp(line, "\r\n") == 0)
			break;

		if ((linelen = check_trim(line)) == 0)
				send_error(500,"Internal Error",NULL,"Too long line in response from server");

#ifdef TIMEOUT
		alarm(TIMEOUT);
#endif
		

		//get content length
		if (strncasecmp(line, "Content-Length:", 15) == 0)
		{
			content_length = atol(&(line[15]));
			where_content_length = lines;
		}
		else if (strncasecmp(line, "Content-Encoding:", 17) == 0)
			where_content_encoding = lines;
		
		else if (strncasecmp(line, "Transfer-Encoding:", 18) == 0)
		{//check for chunking
			tempp = line + 19;
			if (strncasecmp(tempp, "chunked", 7) == 0)
			where_chunked = lines;
		}
		else if (strncasecmp(line, "Content-Type:", 13) == 0)
			where_content_type = lines;
		
		else if (strncasecmp(line, "Location:", 9) == 0)
		{//change suffix in Location: header
			tempp = strrchr(line, '.');
			if(tempp && (tempp + 12 < line + sizeof(line)) &&  
							(!strcasecmp(tempp,".gif") || !strcasecmp(tempp,".png")))
					strcpy(tempp + 4, "_or_jpg");
		}

			//store header info
		if (lines < MAX_HEADERS)
		{
			if(strncasecmp(line, "Connection:", 11) != 0)
			{
			(*hdrs)[lines]=strdup(line);
			lines++;
			}
		}else send_error( 500, "Internal Error",(char*)0,"Too many headers in response from server");

	}

	if(where_content_type > 0)
	{
			tempp = (*hdrs)[where_content_type] + 14;

			if (!strncasecmp(tempp,"text",4))
			{
#ifdef GZIP
					compress = 1;
#endif
					tempp += 4;
					if(!strncasecmp(tempp,"/html",5))
									modify = 1;
			}
			else if(content_length >= 600 || content_length < 0)
			{
				if (!strncasecmp(tempp, "image/jpeg", 10) ||
					!strncasecmp(tempp, "image/pjpeg", 11) ||
					!strncasecmp(tempp, "image/jpg", 9) ||
					!strncasecmp(tempp, "image/pjpg", 10) 
					)
					image = IMG_JPEG;
				else if (!strncasecmp(tempp, "image/gif", 9))
					image = IMG_GIF;
				else if (!strncasecmp(tempp, "image/png", 9))
					image = IMG_PNG;
			}
#ifdef GZIP
			else if (strncasecmp(tempp, "application/",12)==0 && strlen(tempp) > 15)
			{
			tempp += 12;
				if(strncasecmp(tempp,"x-",2) == 0) tempp+=2;

				for (i=0; *compressible[i]; i++)
					if (strncasecmp(tempp,compressible[i],sizeof(compressible[i]) - 1)==0)
					{
						compress = 1;
						break;
					}
			}
#endif
	}

	if(image && !(flags & SUFF_MODIFIED))
	{
			tempp = strrchr(path,'.');
			if(tempp && (!strcasecmp(tempp,".gif") || !strcasecmp(tempp,".png")))
					image =IMG_NONE;
			//we cease to do anything with image. It will confuse browser if
			//content type is other than file suffix.
	}
	
#ifdef GZIP
	//Dont gzip, if either:
	//  content is encoded (e.g already compressed)
	//  browser doesn't accept gzip
	if(compress && ((where_content_encoding > 0) || !(flags & WILLGZIP)))
	{
		compress = 0;
	}
#endif
	
	//Dont modify HTMLs if content is encoded
	if(modify && (where_content_encoding > 0))
		modify = 0;
				
	//log so far
#ifdef LOGGING
	gettimeofday(&newtime,NULL);
	fprintf(logfile, "[%d]   In Headers:\n",logpid);
	for (i = 0; i < lines; i++)
		fprintf(logfile, "[%d]   %s\n",logpid, (*hdrs)[i]);
		fprintf(logfile, "[%d] Image = %d, Modify = %d, Chunked = %d\n",
						logpid, image, modify, (where_chunked > 0));
#ifdef GZIP
	fprintf(logfile, "[%d] WillGZip = %d, Compress = %d\n", logpid, flags & WILLGZIP, compress);
#endif
	gettimeofday(&oldtime,NULL);
#endif

	//if no data only forward header and exit
	if ((strcasecmp(method, "HEAD") == 0) && (status == 304))
	{
		#ifdef LOGGING
		fprintf(logfile, "[%d] Forwading header only.\n",logpid);
		#endif

		//no data, only forward header
		for (i = 0; i < lines; i++)
		{
			fputs((*hdrs)[i], stdout);
			fputs("\r\n",stdout);
		}
		fputs("Connection: close\r\n\r\n", stdout);
		
		//exit
		fflush(stdout);
		return;
	}
	free(method);
	
	//not not workable, stream data and exit	
	if (!compress && !image && !modify)
	{
		#ifdef LOGGING
		fprintf(logfile, "[%d] Forwading header and streaming data.\n",logpid);
		#endif
		//output header
		for (i = 0; i < lines; i++)
		{
			fputs((*hdrs)[i], stdout);
			fputs("\r\n",stdout);
		}
		fputs("Connection: close\r\n\r\n", stdout);

		//stream data through
		for (i = 0; (content_length == -1 || i < content_length) && (ich = getc(sockrfp)) != EOF; ++i)
			fputc(ich, stdout);

		//done
		fflush(stdout);
		return;
	}

	//get data and store for compression
	
	if (content_length > MAX_SIZE)
		send_error( 500, "Internal Error",(char*)0,"Too big file.<br>"
						"Maximal file size allowed for this proxy is "MAX_SIZE_STR".");
			
	if (content_length == -1) inlen = 1024;
	else inlen = content_length;
	
	inbuf=allocstr(inlen + 1);
	
	// unchunk if needed
	if (where_chunked > 0)
	{
		//chunked
		chunkpos = 0;
		fscanf(sockrfp, "%x", &chunklen);
		fgetc(sockrfp); fgetc(sockrfp);

		while (chunklen > 0)
		{
			if (chunklen + chunkpos >= MAX_SIZE)
				send_error( 500, "Internal Error",(char*)0,"Too big file.<br>"
								"Maximal file size allowed for this proxy is "MAX_SIZE_STR".");
					
			for (i = 0; i < chunklen; i++){
				
				if (chunkpos + i >= inlen)
				{
					inbuf=realloc(inbuf,2*inlen + 1);
					inlen*=2;
				}
				inbuf[chunkpos + i] = getc(sockrfp);
			}
			chunkpos += i;

			chunklen = 0;
			fgetc(sockrfp); fgetc(sockrfp);
			fscanf(sockrfp, "%x", &chunklen);
			fgetc(sockrfp); fgetc(sockrfp);
		}

		inlen = chunkpos;

#ifdef LOGGING
	fprintf(logfile, "[%d] Got-Content-Length: %d\n",logpid, inlen);
#endif

	}
	else
	{
		//not chunked
		if (content_length == -1)
		{
			i = 0;
			while ((ich = getc(sockrfp)) != EOF)
			{
				if (i >= MAX_SIZE)
					send_error( 500, "Internal Error",(char*)0,"Too big file.<br> Maximal file size allowed for this proxy is 16 MB.");
					
				if (i >= inlen)
				{
					inbuf=realloc(inbuf,2*inlen + 1);
					inlen*=2;
				}
				
				inbuf[i] = ich;
				i++;
			}
			inlen = i;
#ifdef LOGGING
	fprintf(logfile, "[%d] In Content-Length: %d\n",logpid, inlen);
#endif
		}
		else 
		{
			for (i = 0; i < content_length; i++)
			{
				ich = getc(sockrfp);
				inbuf[i] = ich;
				//should use recv() or read()
				
			} 
			inlen = i;
		}
	}
#ifdef LOGGING
	gettimeofday(&newtime,NULL);
	fprintf(logfile, "[%d] getting data took %ld us\n",logpid,
			timeval_subtract_us(&newtime,&oldtime));
	oldtime=newtime;
#endif

	if(inlen > 50000) // really needed?
		setpriority(PRIO_PROCESS, logpid, 15);
	else if (inlen < 600)
		image = IMG_NONE; //not worth compressing

	if (image)
	{
		switch(image)
		{
				case IMG_PNG:status = png2bitmap(inbuf, inlen, &tempp, 
											 &width, &height, &bpp);
							 quality = 128;
							 break;
				case IMG_GIF:status = gif2bitmap(inbuf, inlen, &tempp, 
											 &width, &height, &bpp);
							 quality = 128;
							 break;
				case IMG_JPEG:status = jpg2bitmap(inbuf, inlen, &tempp, 
											 &width, &height, &bpp);
							 quality = 0;
							 break;
							 
		}
		if (status)
		{//error, forward unchanged
				outbuf = inbuf;
				outlen = inlen;
		}
		else
		{
			    i = width * height;
				if (i < 5000)
						quality += 17;
				else if ((i < 50000)|| (width < 150) || (height < 150))
                        quality += 20;
				else if (i < 250000)
						quality += 23;
				
                else quality += 25;

				//worsest compression ratio allowed 85%(large images) - 90% (small)
				outlen = inlen * 0.85 + 40;
				
				status = bitmap2jpg(tempp, width, height, bpp, quality);
				if (status)
				{
						outbuf = inbuf;
						outlen = inlen;
				}
				else
				{//everything OK
						free((*hdrs)[where_content_type]);
						(*hdrs)[where_content_type] = "Content-Type: image/jpeg";
				}
		}
					
	}

	if(modify)
	{
			inbuf[inlen]='\0';
			status = modify_img_src(inbuf,&outbuf);
			outlen = strlen(outbuf);
	}
#ifdef GZIP
	
	if(compress)
	{
		if(modify) status += gzip(outbuf, outlen, &outbuf, &outlen);
		else status += gzip (inbuf, inlen, &outbuf, &outlen);
	}
#endif
				
	#ifdef LOGGING
	gettimeofday(&newtime,NULL);
	fprintf(logfile, 
					"[%d] Content modification/compression returned %d. It took %ld us.\n",
					logpid, status, timeval_subtract_us(&newtime, &oldtime));
	fprintf(logfile, "[%d] Forwarding header and modified content.\n",logpid);
	fprintf(logfile, "[%d]   Out Headers:\n",logpid);
	gettimeofday(&oldtime,NULL);
	#endif
	//forward header
	for (i = 0; i < lines; i++)
	{
		if (
			(i != where_content_length) &&
			(i != where_chunked))
		{
			#ifdef LOGGING
			fprintf(logfile, "[%d]   %s\n",logpid, (*hdrs)[i]);
			#endif
			fputs((*hdrs)[i], stdout);
			fputs("\r\n",stdout);
		}
	}

	fprintf(stdout, "Content-Length: %d\r\n", outlen);
#ifdef GZIP
	if(compress) fputs("Content-Encoding: gzip\r\n", stdout);
#endif
	
#ifdef LOGGING
	fprintf(logfile, "[%d]   Content-Length: %d\n",logpid, outlen);
#ifdef GZIP
	if(compress) fprintf(logfile, "[%d]   Content-Encoding: gzip\r\n",logpid);
#endif
#endif
	fputs("Connection: close\r\n\r\n", stdout);
#ifdef TIMEOUT
	alarm(TIMEOUT);
#endif

	//forward compressed data
	if (inlen != 0){
			fwrite(outbuf, 1, outlen, stdout);
	}
	fflush(stdout);
#ifdef LOGGING
	fsync(1);
	gettimeofday(&newtime,NULL);
	fprintf(logfile, "[%d] forwarding took %ld us\n",logpid,
			timeval_subtract_us(&newtime, &oldtime));
#endif
}


static void
proxy_ssl( char* method, char* host, int port, char* protocol, FILE* sockrfp, FILE* sockwfp )
    {
    int client_read_fd, server_read_fd, client_write_fd, server_write_fd;
    struct timeval timeout;
    fd_set fdset;
    int maxp1, r;
    char buf[10000];

	free(method);
    /* Return SSL-proxy greeting header. */
    (void) fputs( "HTTP/1.0 200 Connection established\r\n\r\n", stdout );
    (void) fflush( stdout );
    /* Now forward SSL packets in both directions until done. */
    client_read_fd = fileno( stdin );
    server_read_fd = fileno( sockrfp );
    client_write_fd = fileno( stdout );
    server_write_fd = fileno( sockwfp );
#ifdef TIMEOUT
    timeout.tv_sec = TIMEOUT;
    timeout.tv_usec = 0;
#endif
    if ( client_read_fd >= server_read_fd )
	maxp1 = client_read_fd + 1;
    else
	maxp1 = server_read_fd + 1;
    (void) alarm( 0 );
    for (;;)
	{
	FD_ZERO( &fdset );
	FD_SET( client_read_fd, &fdset );
	FD_SET( server_read_fd, &fdset );
#ifdef TIMEOUT
	r = select( maxp1, &fdset, (fd_set*) 0, (fd_set*) 0, &timeout );
	if ( r == 0 )
	    send_error( 408, "Request Timeout", (char*) 0, "Request timed out." );
	else 
#else
	r = select( maxp1, &fdset, (fd_set*) 0, (fd_set*) 0, (struct timeval*)0 );
#endif
		if ( FD_ISSET( client_read_fd, &fdset ) )
	    {
	    r = read( client_read_fd, buf, sizeof( buf ) );
	    if ( r <= 0 )
		break;
	    r = write( server_write_fd, buf, r );
	    if ( r <= 0 )
		break;
	    }
	else if ( FD_ISSET( server_read_fd, &fdset ) )
	    {
	    r = read( server_read_fd, buf, sizeof( buf ) );
	    if ( r <= 0 )
		break;
	    r = write( client_write_fd, buf, r );
	    if ( r <= 0 )
		break;
	    }
	}
    }


#ifdef TIMEOUT
static void
sigcatch( int sig )
    {
    send_error( 408, "Request Timeout", (char*) 0, "Request timed out." );
    }
#endif

//If it returns line length > 0, line was correctly ended with newline - use to check output
//from fgets(). 
static int
check_trim( char* line )
    {
    int k,l;

    k = l = strlen( line );
	
    while (l > 0 && (line[l-1] == '\n' || line[l-1] == '\r'))
		line[--l] = '\0';

	return (k != l)? l : 0;
    }


static void
send_error( int status, char* title, char* extra_header, char* text )
    {
    send_headers( status, title, extra_header, "text/html", -1, -1 );
    (void) printf( "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\n<BODY BGCOLOR=\"#cc9999\"><H4>%d %s</H4>\n", status, title, status, title );
    (void) printf( "%s\n", text );
    (void) printf( "<HR>\n<ADDRESS><A HREF=\"%s\">%s</A></ADDRESS>\n</BODY></HTML>\n", SERVER_URL, SERVER_NAME );
    (void) fflush( stdout );

    //log
#ifdef LOGGING
    fprintf(logfile, "[%d] ERROR - %d %s ( %s )\n", logpid, status, title, text);
    fprintf(logfile, "[%d] Finished.\n",logpid);
    fclose(logfile);
#endif
    exit( 1 );
    }


static void
send_headers( int status, char* title, char* extra_header, char* mime_type, int length, time_t mod )
    {
    time_t now;
    char timebuf[100];

    (void) printf( "%s %d %s\r\n", PROTOCOL, status, title );
    (void) printf( "Server: %s\r\n", SERVER_NAME );
    now = time( (time_t*) 0 );
    (void) strftime( timebuf, sizeof(timebuf), RFC1123FMT, gmtime( &now ) );
    (void) printf( "Date: %s\r\n", timebuf );
    if ( extra_header != (char*) 0 )
	(void) printf( "%s\r\n", extra_header );
    if ( mime_type != (char*) 0 )
	(void) printf( "Content-Type: %s\r\n", mime_type );
    if ( length >= 0 )
	(void) printf( "Content-Length: %d\r\n", length );
    if ( mod != (time_t) -1 )
	{
	(void) strftime( timebuf, sizeof(timebuf), RFC1123FMT, gmtime( &mod ) );
	(void) printf( "Last-Modified: %s\r\n", timebuf );
	}
    (void) printf( "Connection: close\r\n" );
    (void) printf( "\r\n" );
    }

char *allocstr(size_t size)
{
	char* str=(char*)malloc(size);
	if(!str) {
		fputs("Virtual memory exhausted!\n",stderr);
		exit(3);
	}
	return str;
}
 

long timeval_subtract_us (struct timeval *x, struct timeval *y)
{
	struct timeval result;
	
        /* Perform the carry for the later subtraction by updating Y. */
        if (x->tv_usec < y->tv_usec) {
          int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
          y->tv_usec -= 1000000 * nsec;
          y->tv_sec += nsec;
        }
        if (x->tv_usec - y->tv_usec > 1000000) {
          int nsec = (x->tv_usec - y->tv_usec) / 1000000;
          y->tv_usec += 1000000 * nsec;
          y->tv_sec -= nsec;
        }
      
        /* Compute the time remaining to wait.
           `tv_usec' is certainly positive. */
        result.tv_sec = x->tv_sec - y->tv_sec;
        result.tv_usec = x->tv_usec - y->tv_usec;
      
        return 1000000*result.tv_sec + result.tv_usec;
}

	
			
